"""
    涉及城市上月和上上月的新房二手房房价及环比，城市各区域新房房价及环比的文章(房价1)
"""
import copy
import datetime
import os
import random
import re
import time
import traceback
from typing import Dict

from langchain import PromptTemplate

try:
    from bot.openai_bot import OpenAIBot
    from conf.config import logger, BASE_DIR, config
    from db.building_dao import BuildingDAO
    from db.house_price_dao import HousePriceDAO
    from prompts.prompt_templates import BUILDING_INFO_BASE_PROMPT, HOUSE_PRICE_INFO_BASE_PROMPT, \
        ARTICLE_WITH_NEW_HOUSE_PRICE_INFO_PROMPT, ABSTRACT_AND_KEYWORDS_PROMPT, PARAGRAPH1_PROMPT, \
        PARAGRAPH2_PROMPT, PARAGRAPH3_PROMPT
    from utils.constants import EN_TO_CN, SALE_STATUS
    from utils.format_article import count_words, all_has_tags, remove_stop_words, remove_duplicate_paragraph_from_content, \
        extract_abstract_and_keywords
    from utils.laod_data import load_excel_sheet, get_city_name, load_title_list
    from utils.output_file import output_txt, output_json
except ModuleNotFoundError:
    import os
    import sys
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # 离开IDE也能正常导入自己定义的包
    from bot.openai_bot import OpenAIBot
    from conf.config import logger, BASE_DIR, config
    from db.building_dao import BuildingDAO
    from db.house_price_dao import HousePriceDAO
    from prompts.prompt_templates import BUILDING_INFO_BASE_PROMPT, HOUSE_PRICE_INFO_BASE_PROMPT, \
        ARTICLE_WITH_NEW_HOUSE_PRICE_INFO_PROMPT, ABSTRACT_AND_KEYWORDS_PROMPT, PARAGRAPH1_PROMPT, \
        PARAGRAPH2_PROMPT, PARAGRAPH3_PROMPT
    from utils.constants import EN_TO_CN, SALE_STATUS
    from utils.format_article import count_words, all_has_tags, remove_stop_words, remove_duplicate_paragraph_from_content, \
        extract_abstract_and_keywords
    from utils.laod_data import load_excel_sheet, get_city_name, load_title_list
    from utils.output_file import output_txt, output_json


def get_year_and_month_of_the_last_month():
    """
    获取上个月的年份和月份
    :return:
    """
    now = datetime.datetime.now()  # 获取当前时间
    now_last_month = now - datetime.timedelta(days=now.day)  # 获取上个月的这个时间点
    year_last_month = now_last_month.year  # 获取上个月的年份
    month_last_month = now_last_month.month  # 获取上个月的月份

    return year_last_month, month_last_month


def get_year_and_month_of_two_months_ago():
    """
    获取上上个月的年份和月份
    :return:
    """

    today = datetime.date.today()
    first_day_of_this_month = datetime.date(today.year, today.month, 1)
    last_day_of_last_month = first_day_of_this_month - datetime.timedelta(days=1)
    first_day_of_last_month = datetime.date(last_day_of_last_month.year, last_day_of_last_month.month, 1)
    last_day_of_two_months_ago = first_day_of_last_month - datetime.timedelta(days=1)
    year_of_two_months_ago = last_day_of_two_months_ago.year
    month_of_two_months_ago = last_day_of_two_months_ago.month

    return year_of_two_months_ago, month_of_two_months_ago


def get_house_price_info(city_name: str) -> Dict:
    year_of_last_month, month_of_last_month = get_year_and_month_of_the_last_month()
    year_of_two_months_ago, month_of_two_months_ago = get_year_and_month_of_two_months_ago()

    # 城市新房数据
    house_price_dao = HousePriceDAO()
    city_new_house_price_last_moth = house_price_dao.get_city_house_price(year=year_of_last_month, month=month_of_last_month, city_name=city_name, house_type="新房")
    city_new_house_price_two_months_ago = house_price_dao.get_city_house_price(year=year_of_two_months_ago, month=month_of_two_months_ago, city_name=city_name, house_type="新房")
    month_on_month_change_city_new_house_price = (city_new_house_price_last_moth - city_new_house_price_two_months_ago) / city_new_house_price_two_months_ago

    # 城市二手房数据
    city_second_hand_house_price_last_moth = house_price_dao.get_city_house_price(year=year_of_last_month, month=month_of_last_month, city_name=city_name, house_type="二手房")
    city_second_hand_house_price_two_months_ago = house_price_dao.get_city_house_price(year=year_of_two_months_ago, month=month_of_two_months_ago, city_name=city_name, house_type="二手房")
    month_on_month_change_city_second_hand_house_price = (city_second_hand_house_price_last_moth - city_second_hand_house_price_two_months_ago) / city_second_hand_house_price_two_months_ago

    # 区域新房数据
    district_new_house_price_last_moth = house_price_dao.get_district_house_price(year=year_of_last_month, month=month_of_last_month, city_name=city_name, house_type="新房")
    district_new_house_two_months_ago = house_price_dao.get_district_house_price(year=year_of_two_months_ago, month=month_of_two_months_ago, city_name=city_name, house_type="新房")

    data = {
        "city_new_house_price_last_moth": city_new_house_price_last_moth,
        "city_new_house_price_two_months_ago": city_new_house_price_two_months_ago,
        "city_second_hand_house_price_last_moth": city_second_hand_house_price_last_moth,
        "city_second_hand_house_price_two_months_ago": city_second_hand_house_price_two_months_ago,
        "month_on_month_change_city_new_house_price": f"{round(month_on_month_change_city_new_house_price * 100, 2)}%",
        "month_on_month_change_city_second_hand_house_price": f"{round(month_on_month_change_city_second_hand_house_price * 100, 2)}%",

        "district_new_house_price_last_moth": district_new_house_price_last_moth,
        "district_new_house_two_months_ago": district_new_house_two_months_ago,
        "month_of_last_month": month_of_last_month,
        "month_of_two_months_ago": month_of_two_months_ago,
        "year_of_last_month": year_of_last_month,
        "year_of_two_months_ago": year_of_two_months_ago,
    }

    return data


def format_district_house_price_info(house_price_info: Dict) -> str:
    """
    格式化区域房价信息
    :param house_price_info: 房价信息
    :return: 格式化后的字符串
    """
    house_price_info_string = HOUSE_PRICE_INFO_BASE_PROMPT
    district_new_house_two_months_ago = house_price_info.get("district_new_house_two_months_ago")
    district_new_house_price_last_moth = house_price_info.get("district_new_house_price_last_moth")

    district_new_house_two_months_ago_set = set()
    for item in district_new_house_two_months_ago:
        district_new_house_two_months_ago_set.add(item['district_name'])

    district_new_house_price_last_moth_set = set()
    for item in district_new_house_price_last_moth:
        district_new_house_price_last_moth_set.add(item['district_name'])

    # 用交集来找出共同存在的district_name，因为不是每个月都爬取了所有区的房价，可能有缺失
    district_set = district_new_house_two_months_ago_set & district_new_house_price_last_moth_set

    district_house_price_info = ""
    for index, district in enumerate(district_set):
        for obj in district_new_house_two_months_ago:
            if obj.get("district_name") == district:
                new_house_price_two_months_ago = obj.get("house_price")
                break
        for obj1 in district_new_house_price_last_moth:
            if obj1.get("district_name") == district:
                new_house_price_last_moth = obj1.get("house_price")
                break

        month_on_month_change = (new_house_price_last_moth - new_house_price_two_months_ago) / new_house_price_two_months_ago

        district_house_price_info = ''.join([district_house_price_info, f'| {district} | {str(new_house_price_last_moth)} | {str(new_house_price_two_months_ago)} | {f"{round(month_on_month_change* 100, 2) }%"} |', '\n'])

    return district_house_price_info


def generate_article(prompt, filename, title, city_name):
    """
    生产文章
    :param prompt: 提交gpt的prompt
    :param filename: 保存的文件名
    :param title: 文章标题
    :param city_name: 文章所属的城市名
    :return:
    """
    data = dict()
    openai_bot = OpenAIBot(session_id=filename)
    prompt2 = PARAGRAPH1_PROMPT
    prompt3 = PARAGRAPH2_PROMPT
    prompt4 = PARAGRAPH3_PROMPT
    prompt5 = ABSTRACT_AND_KEYWORDS_PROMPT
    answer1 = openai_bot.reply(question=prompt, session_round=1)
    answer2 = openai_bot.reply(question=prompt2, session_round=2)
    answer3 = openai_bot.reply(question=prompt3, session_round=3)
    answer4 = openai_bot.reply(question=prompt4, session_round=4)
    answer5 = openai_bot.reply(question=prompt5, session_round=5)

    # 提取小标题
    pattern = r'<p><strong>.*?</strong></p>'
    subtitles = re.findall(pattern, answer1)
    # 正文段落
    paragraphs = [answer2, answer3, answer4]

    # 每段正文的第一段与小标题一致，则应该删除正文的第一段
    paragraphs = remove_duplicate_paragraph_from_content(subtitles=subtitles, paragraphs=paragraphs)

    # 带停用词的文章
    content_within_stop_words = "\n".join([subtitles[0], paragraphs[0], subtitles[1], paragraphs[1], subtitles[2], paragraphs[2], ])

    # 删除小标题的停用词
    for index, subtitle in enumerate(subtitles):
        subtitles[index] = remove_stop_words(text=subtitle, stop_words=config.get("stop_words_subtitle"))
    # 删除正文的停用词
    for index, paragraph in enumerate(paragraphs):
        paragraphs[index] = remove_stop_words(text=paragraph, stop_words=config.get("stop_words"))

    # 不带停用词的文章
    content = "\n".join([subtitles[0], paragraphs[0], subtitles[1], paragraphs[1], subtitles[2], paragraphs[2], ])

    abstract, keywords = extract_abstract_and_keywords(answer5)
    data.update({
        "id": str(filename), "title": title, "city_name": city_name,
        "abstract": abstract, "keywords": keywords, "content": content
    })

    # 判断摘要字数是否在限值内
    abstract_word_count = count_words(data.get("abstract"), excluded_char_ist=[])
    if abstract_word_count > config.get("word_limit_for_abstract"):
        raise ValueError(f"摘要字数超限值，共{abstract_word_count}字")

    # 判断正文字数是否足够
    num = count_words(data.get("content"), excluded_char_ist=config.get("excluded_char_ist"))
    if num <= config.get("word_limit_for_content").get("article_with_new_house_price"):
        raise ValueError(f"正文数字不足，只有{num}字")

    # 判断正文是否都有标签
    if not all_has_tags(text=data.get("content"), startswith_tags=config.get("startswith_tags"),
                        endswith_tags=config.get("endswith_tags")):
        raise ValueError(f"正文有些段落标签错误")

    # 输出到json(中文为不可读的Unicode编码)
    output_json(
        output_full_path=os.path.join(BASE_DIR, f"output/{filename}.json"),
        data=data, ensure_ascii=True
    )

    # prompt输出到txt文件
    output_txt(
        output_full_path=os.path.join(BASE_DIR, f'output/{filename}_prompt.txt'),
        txt="\n".join([prompt, prompt2, prompt3, prompt4, prompt5])
    )

    # 文章结果输出到txt文件
    text = "\n\n".join([answer1, answer2, answer3, answer4, answer5])
    count = count_words(text, excluded_char_ist=config.get("excluded_char_ist"))
    text = f"{text}\n\n==========\n正文字数统计：{count}"
    output_txt(
        output_full_path=os.path.join(BASE_DIR, f"output/{filename}.txt"),
        txt=text
    )

    # content_within_stop_words输出到txt文件
    output_txt(
        output_full_path=os.path.join(BASE_DIR, f'output/{filename}_content_within_stop_words.txt'),
        txt=content_within_stop_words
    )


def main():
    fail_list = list()
    title_list = load_title_list()
    for item in title_list:
        filename = item.get("filename")
        title = item.get("title")
        city_name = item.get("city_name")
        try:
            # 从城市名中获取标题名
            city_name = get_city_name(title, original_city_name=city_name)
            if not city_name:
                logger.warning(f'文件名：{filename} 标题：{title} 未识别到城市名')
                raise ValueError("未识别到城市名")

            # 查询数据库获取房价信息
            house_price_info = get_house_price_info(city_name=city_name)
            for key, value in house_price_info.items():
                if not value:
                    logger.warning(f"数据库获取到的房价信息：{house_price_info}")
                    raise ValueError("数据库未获取到的房价信息")

            # 将房价信息格式化为字符串
            district_house_price_info = format_district_house_price_info(house_price_info)
            # 渲染模板 步骤1
            prompt1 = PromptTemplate(
                input_variables=[
                    "city_name", "district_house_price_info",
                    "year_of_last_month", "month_of_last_month",
                    "year_of_two_months_ago", "month_of_two_months_ago",
                    "city_new_house_price_last_moth", "month_on_month_change_city_new_house_price",
                    "city_second_hand_house_price_last_moth", "month_on_month_change_city_second_hand_house_price",
                ],
                template=HOUSE_PRICE_INFO_BASE_PROMPT,
                template_format="jinja2"
            )
            prompt1_formatted = prompt1.format(
                district_house_price_info=district_house_price_info,
                city_name=city_name,
                **house_price_info
            )

            # 渲染模板 步骤2
            prompt2 = PromptTemplate(
                input_variables=["title", "city_name", "house_price_info", "date"],
                template=ARTICLE_WITH_NEW_HOUSE_PRICE_INFO_PROMPT,
                template_format="jinja2"
            )
            prompt2_formatted = prompt2.format(title=title, city_name=city_name, house_price_info=prompt1_formatted,
                                               date=datetime.date.today().strftime('%Y-%m-%d'))

            generate_article(prompt=prompt2_formatted, filename=f'{filename}', title=title, city_name=city_name)
            time.sleep(random.uniform(10, 15))

        except Exception:
            error_str = traceback.format_exc()
            logger.error(error_str)
            fail_list.append({"filename": filename, "log": error_str})

    logger.warning(f'失败列表：\n{[item.get("filename") for item in fail_list]}')
    logger.warning(f'失败列表详情：\n{fail_list}')


if __name__ == '__main__':
    main()
